/*
 *  linux/mm/memory.c
 *
 *  (C) 1991  Linus Torvalds
 */

/*  // 需求加载是从01.12.91开始编写的-在程序编制表中似乎是最重要的程序 并且应该很容易编制的-Linus
 * demand-loading started 01.12.91 - seems it is high on the list of
 * things wanted, and it should be easy to implement. - Linus
 */

/*
 * Ok, demand-loading was easy, shared pages a little bit tricker. Shared
 * pages started 02.12.91, seems to work. - Linus.
 *
 * Tested sharing by executing about 30 /bin/sh: under the old kernel it
 * would have taken more than the 6M I have free, but it worked well as
 * far as I could see.
 *
 * Also corrected some "invalidate()"s - I wasn't doing enough of them.
 */ // 需求加载是比较容易编写的 而共享页面却需要有点技巧 共享页面程序是从1991年12月1日开始编写的 好像能够工作-Linus
// 通过执行大约30个/bin/sh对共享操作进行了测试 在老内核当中需要占用多于6MB的内存 而目前却不用 现在看来工作的很好
#include <signal.h> // 信号头文件 定义信号符号常量 信号结构以及信号操作函数原型

#include <asm/system.h> // 系统头文件 定义了设置或修改描述符/中断门等的嵌入式汇编宏

#include <linux/sched.h>    //调度程序头文件 定义了任务结构task_struct 初始任务0的数据
#include <linux/head.h>     // head头文件 定义了段描述符的简单结构 和几个选择符常量
#include <linux/kernel.h>   // 内核头文件 含有一些内核常用函数的原形定义

volatile void do_exit(long code);   // 进程退出处理函数
// 显示内存已用完出错信息 并退出
static inline volatile void oom(void)
{
	printk("out of memory\n\r");
	do_exit(SIGSEGV);   // do_exit 应该使用退出代码 这里用了信号值SIGSEV（11）相同值的出错码含义是 资源暂时不可用 正好同义
}
// 刷新页变换高速缓冲宏函数 为了提高地址转换的效率 CPU将最近使用的页表数据存放在芯片高速缓冲中 在修改过页表信息之后 就需要刷新该缓冲区 这里使用重新加载页目录基址寄存器cr3的方法来进行刷新 下面eax=0 是页目录的基址
#define invalidate() \
__asm__("movl %%eax,%%cr3"::"a" (0))
// 下面定义若需要改动 则需要与head.s等文件中的相关信息一起改变
/* these are not to be changed without changing head.s etc */ // linux-0.11内核模式支持的最大内存容量是16MB 可以修改这些定义以适合更多的内存
#define LOW_MEM 0x100000                    // 内存低端（1MB）
#define PAGING_MEMORY (15*1024*1024)        // 分页内存15MB 主内存区最多15MB
#define PAGING_PAGES (PAGING_MEMORY>>12)    // 分页侯的物理内存页数
#define MAP_NR(addr) (((addr)-LOW_MEM)>>12) // 指定内存地址映射为页号
#define USED 100                            // 页面被占用标志 参见405行
// 该宏用于判断给定地址是否位于当前进程的代码段中 参见252行
#define CODE_SPACE(addr) ((((addr)+4095)&~4095) < \
current->start_code + current->end_code)
// 全局变量 存放实际物理内存最高端地址
static long HIGH_MEMORY = 0;
// 复制1页内存（4k字节）
#define copy_page(from,to) \
__asm__("cld ; rep ; movsl"::"S" (from),"D" (to),"c" (1024):"cx","di","si")
// 内存映射字节图（1B代表1页）每个页面对应的字节用于标志页面当前被引用（占用）次数
static unsigned char mem_map [ PAGING_PAGES ] = {0,};

/*  // 获取第一个（世界上是最后1个）空闲页面 并标记为已使用 如果没有空闲页面 就返回0
 * Get physical address of first (actually last :-) free page, and mark it
 * used. If no free pages left, return 0.
 */ // 上面%4寄存器实际指向mem_map[]内存字节图的最后一个字节 本函数从字节图末端开始向前扫描所有页面标志（页面总数为PAGING_PAGES） 若有页面空闲（其内存映像字节为0）则返回页面地址 注意！本函数只是指出在主内存区的一页空闲页面 但并没有映射到某个进程的线性地址去 后面的put_page()函数就是用来作映射的
unsigned long get_free_page(void)
{
register unsigned long __res asm("ax");

__asm__("std ; repne ; scasb\n\t"   // 方向位置位 将al（0）与对应每个页面的（di）内容比较
	"jne 1f\n\t"                    // 如果没有等于0的字节 则跳转结束（返回0）
	"movb $1,1(%%edi)\n\t"          // 将对应页面的内存映像位置1
	"sall $12,%%ecx\n\t"            // 页面数 * 4KB = 相对页面起始地址
	"addl %2,%%ecx\n\t"             // 再加上低端内存地址 即获得页面实际物理起始地址
	"movl %%ecx,%%edx\n\t"          // 将页面世界起始地址->edx寄存器
	"movl $1024,%%ecx\n\t"          // 寄存器ecx置计数值1024
	"leal 4092(%%edx),%%edi\n\t"    // 将4092+edx的位置->edi（该页面的末端）
	"rep ; stosl\n\t"               // 将edi所指内存清零（反方向 即将该页面清零）
	"movl %%edx,%%eax\n"            // 将页面起始地址->eax（返回值）
	"1:"
	:"=a" (__res)
	:"0" (0),"i" (LOW_MEM),"c" (PAGING_PAGES),
	"D" (mem_map+PAGING_PAGES-1)
	:"di","cx","dx");
return __res;                       // 返回空闲页面地址（如果无空闲则返回0）
}

/*  // 释放物理地址‘addr’开始的一页内存 用于‘函数’ free_page_tables（）
 * Free a page of memory at physical address 'addr'. Used by
 * 'free_page_tables()'
 */ // 释放物理地址addr开始的一页面内存 1MB以下的内存空间用于内核程序和缓冲 不作为分配页面的内存空间
void free_page(unsigned long addr)
{
	if (addr < LOW_MEM) return;         // 如果物理地址 addr 小于内存低端（1MB） 则返回
	if (addr >= HIGH_MEMORY)            // 如果物理地址 addr >= 内存最高端 则显示出错信息
		panic("trying to free nonexistent page");
	addr -= LOW_MEM;                    // 物理地址减去低端内存地址 再除以4KB 得页面号
	addr >>= 12;
	if (mem_map[addr]--) return;        // 如果对应内存页面映射字节不等于0 则减1返回
	mem_map[addr]=0;                    // 否则置对应页面映射字节为0 并显示出错信息 死机
	panic("trying to free free page");
}

/*  // 下面函数释放页表连续的内存块 exit（）需要该函数 与copy_page_tables（）类似 该函数仅处理4MB的内存块
 * This function frees a continuos block of page tables, as needed
 * by 'exit()'. As does copy_page_tables(), this handles only 4Mb blocks.
 */ // 根据指定的线性地址和限长（页表个数） 释放对应内存页表所指定的内存块并置表项空闲 页目录位于物理地址0开始处 共1024项 也占4KB内存 每个页表项对应一页物理内存（4KB） 目录项和页表项的大小均为4KB 参数：from-起始基地址 size-释放的长度
int free_page_tables(unsigned long from,unsigned long size)
{
	unsigned long *pg_table;
	unsigned long * dir, nr;

	if (from & 0x3fffff)    // 要释放内存块的地址需以4MB为边界
		panic("free_page_tables called with wrong alignment");
	if (!from)              // 出错 试图释放内核和缓冲所占空间
		panic("Trying to free up swapper memory space");
	size = (size + 0x3fffff) >> 22; // 计算所占页目录项数（4MB的进位整数倍） 即所占页表数
	dir = (unsigned long *) ((from>>20) & 0xffc); /* _pg_dir = 0 */
	for ( ; size-->0 ; dir++) { // 下句计算起始目录项 对应的目录项号=from>>22 因每项占4字节 且由于页目录项是从物理地址0开始 因此实际目录项指针=目录项号<<2 也即（from>>20） 与0xffc进行与操作确保目录项指针范围有效 size现在是需要被释放内存的目录项数
		if (!(1 & *dir))        // 如果该目录项无效（P位=0） 则继续
			continue;           // 取目录项中页表地址
		pg_table = (unsigned long *) (0xfffff000 & *dir);   // 取目录项中页表地址
		for (nr=0 ; nr<1024 ; nr++) {   // 每个页表有1024个页项
			if (1 & *pg_table)  // 若该页表项有效（P位=1） 则释放对应内存页
				free_page(0xfffff000 & *pg_table);
			*pg_table = 0;      // 该页表项内存清零
			pg_table++;         // 指向页表中下一项
		}
		free_page(0xfffff000 & *dir);   // 释放该页表所占内存页面
		*dir = 0;               // 对相应页表的目录项清零
	}
	invalidate();               // 刷新页变换高速缓冲
	return 0;
}

/*  // 好了 下面是内存管理mm中最复杂的程序之一。它通过只复制内存页面来复制一定范围内线性地址中的内容 希望代码中没有错误 因为我不想再调试这块代码了
 *  Well, here is one of the most complicated functions in mm. It
 * copies a range of linerar addresses by copying only the pages.
 * Let's hope this is bug-free, 'cause this one I don't want to debug :-)
 *  // 注意！我们并不复制人核内存块--内存块的地址需要是4MB的倍数（正好一个页目录项对应的内存大小） 因为这样处理可使函数很简单 不管怎样 它仅被fork（）使用
 * Note! We don't copy just any chunks of memory - addresses have to
 * be divisible by 4Mb (one page-directory entry), as this makes the
 * function easier. It's used only by fork anyway.
 *  // 注意！！当from等于0时 是在为第一次fork（）调用复制内核空间 此时我们不想复制整个页目录项对应的内存 因此这样做会导致内存严重的浪费-我们只复制头160个页面-对应640KB 即使是赋值这些页面也已经超出我们的需求 单着不会占用更多的内存 在低1MB内存范围内我们不执行写时复制操作 所以这些页面可以与内核共享 因此这是nr=xxxx的特殊情况（nr在程序中指页面数）
 * NOTE 2!! When from==0 we are copying kernel space for the first
 * fork(). Then we DONT want to copy a full page-directory entry, as
 * that would lead to some serious memory waste - we just copy the
 * first 160 pages - 640kB. Even that is more than we need, but it
 * doesn't take any more memory - we don't copy-on-write in the low
 * 1 Mb-range, so the pages can be shared with the kernel. Thus the
 * special case for nr=xxxx.
 */ // 复制指定线性地址和长度（页表个数） 内存对应的页目录项和页表 从而被复制的页目录和页表对应的原物理内存区被共享使用 复制指定地址和长度的内存对应的页目录项 需申请页面来存放新页表 原内存区被共享 此后两个进程将共享内存区 直到有一个进程执行写操作时， 才分配新的内存页（写时复制机制）
int copy_page_tables(unsigned long from,unsigned long to,long size)
{
	unsigned long * from_page_table;
	unsigned long * to_page_table;
	unsigned long this_page;
	unsigned long * from_dir, * to_dir;
	unsigned long nr;
    // 源地址和目的地址都需要是在4MB的内存边界地址上 否则出错 死机
	if ((from&0x3fffff) || (to&0x3fffff))
		panic("copy_page_tables called with wrong alignment");
	from_dir = (unsigned long *) ((from>>20) & 0xffc); /* _pg_dir = 0 */    // 取得源地址和目的地址的目录项指针（from_dir和to_dir）参见对115句的注释
	to_dir = (unsigned long *) ((to>>20) & 0xffc);
	size = ((unsigned) (size+0x3fffff)) >> 22;  // 计算要复制的内存块占用的页表数（即目录项数）
	for( ; size-->0 ; from_dir++,to_dir++) {    // 下面开始对每个占用的页表一次进行复制操作
		if (1 & *to_dir)    // 如果目的目录项指定的页表已经存在（P=1） 则出错 死机
			panic("copy_page_tables: already exist");
		if (!(1 & *from_dir))   // 如果此源目录项未被使用 则不用复制对应页表 跳过
			continue;
		from_page_table = (unsigned long *) (0xfffff000 & *from_dir);   // 取当前源目录项中页表的地址->from_page_table
		if (!(to_page_table = (unsigned long *) get_free_page()))   // 为目的页表取一页空闲内存 如果返回是0 则说明没有申请到空闲内存页面 返回值=-1 退出
			return -1;	/* Out of memory, see freeing */
		*to_dir = ((unsigned long) to_page_table) | 7;  // 设置目的目录项信息 7是标志信息
		nr = (from==0)?0xA0:1024;   // 针对当前处理的页表 设置需复制的页面数 如果是在内核空间 则仅需复制头160页 否则需要复制1个页表中所有1024页面
		for ( ; nr-- > 0 ; from_page_table++,to_page_table++) {
			this_page = *from_page_table;   // 取源页表项内容
			if (!(1 & this_page))           // 如果当前源页面没有使用 则不用复制
				continue;
			this_page &= ~2;    // 复位页表项中R/W标志（置0）
			*to_page_table = this_page; // 蒋该页表项复制到目的页表中
			if (this_page > LOW_MEM) {  // 如果该页表项所指页面的地址在1MB以上 则需要设置内存页面映射数组mem_map[] 于是计算页面号 并以它为索引在页面映射数组相应项中增加引用次数 1MB以下是内核代码页面
				*from_page_table = this_page;   // 令源页表项所指内存页也为只读 因为现在开始有两个进程公用内存区了 若其中一个内存需要进行写操作 则可以通过页异常的写保护处理 为执行写操作的进程分配一页新的空闲页面 即进行写时复制的操作
				this_page -= LOW_MEM;       // 上句：令源页表项也只读
				this_page >>= 12;
				mem_map[this_page]++;
			}
		}
	}
	invalidate();   // 刷新页变换高速缓冲
	return 0;
}

/*  // 下面函数将一内存页面防止在指定地址处 它返回也买你的物理地址 如果内存不够（在访问页表或页面时） 则返回0
 * This function puts a page in memory at the wanted address.
 * It returns the physical address of the page gotten, 0 if
 * out of memory (either when trying to access page-table or
 * page.)
 */ // 把一物理内存页面映射到指定的线性地址处 主要工作是在页表中设置指定页面的信息 若成功 则返回页面地址
unsigned long put_page(unsigned long page,unsigned long address)
{
	unsigned long tmp, *page_table;
    // 注意！！！ 这里使用了页目录基址 pg_dir = 0 的条件
/* NOTE !!! This uses the fact that _pg_dir=0 */
    // 如果申请的页面位置低于LOW_MEM（1MB）或超出系统实际含有内存高端HIGH_MEMORY 则发出警告
	if (page < LOW_MEM || page >= HIGH_MEMORY)
		printk("Trying to put page %p at %p\n",page,address);
	if (mem_map[(page-LOW_MEM)>>12] != 1)   // 如果申请的页面在内存页面映射字节图中没有置位 则显示警告信息
		printk("mem_map disagrees with %p at %p\n",page,address);
	page_table = (unsigned long *) ((address>>20) & 0xffc); // 计算指定地址在页目录表中对应的目录项指针
	if ((*page_table)&1)    // 如果该目录项有效（P=1）（即指定的页表在内存中） 则从中取得指定页表的地址->page_table
		page_table = (unsigned long *) (0xfffff000 & *page_table);
	else {  // 否则 申请空闲页面给页表使用 并在对应目录项中置相应标志 然后将该页表的地址保存在page_table中
		if (!(tmp=get_free_page()))
			return 0;
		*page_table = tmp|7;
		page_table = (unsigned long *) tmp;
	}   // 在页表中设置指定地址的物理内存页面的页表项内容 每个页表共可用1024项
	page_table[(address>>12) & 0x3ff] = page | 7;
/* no need for invalidate */    // 不需要刷新页变换高速缓冲
	return page;    // 返回页面地址
}
// 取消写堡胡页面函数 用于页异常终端过程中写保护异常的处理（写时复制） 输入参数为页表项指针
void un_wp_page(unsigned long * table_entry)
{
	unsigned long old_page,new_page;

	old_page = 0xfffff000 & *table_entry;   // 取指定页表项内物理页面地址
	if (old_page >= LOW_MEM && mem_map[MAP_NR(old_page)]==1) {  // 若页面地址大于内存低端LOW_MEM（1MB） 且其在页面映射字节图数组中值为1（表示仅被引用1次 页面未被共享） 则在该页面页表项中置R/W标志（可写） 并刷新页变换高速缓冲后返回
		*table_entry |= 2;
		invalidate();
		return;
	}
	if (!(new_page=get_free_page()))    // 否则 在主内存区申请一页空闲页面
		oom();                          // 内存不够处理
	if (old_page >= LOW_MEM)    // 如果原页面地址大于内存低端（则意味着mem_map[]>1，页面时共享的） 则将原页面的页面映射值递减1 然后将指定页表项内容更新为新页面的地址 并置可读写标志（U/S R/W，P） 刷新页变换高速缓冲 最后将原页面内容复制到新页面
		mem_map[MAP_NR(old_page)]--;
	*table_entry = new_page | 7;
	invalidate();
	copy_page(old_page,new_page);
}	

/*  // 当用户试图往一个共享页面上写时， 该函数处理已存在的内存页面（写时复制）它时通过将页面复制到一个新地址上并递减原页面的共享页面计数值实现的 如果它在代码空间 我们就以段错误信息退出
 * This routine handles present pages, when users try to write
 * to a shared page. It is done by copying the page to a new address
 * and decrementing the shared-page counter for the old page.
 *
 * If it's in code space we exit with a segment error.
 */ // 页异常终端处理调用的C函数 写共享页面处理函数 在page.s 程序中被调用 参数 error_code 是由CPU自动产生 address是页面线性地址 写页面共享时，需复制页面（写时复制）
void do_wp_page(unsigned long error_code,unsigned long address)
{
#if 0   // 我们现在还不能这样做 因为 estdio 库会在代码空间执行写操作
/* we cannot do this yet: the estdio library writes to code space */
/* stupid, stupid. I really want the libc.a from GNU */
	if (CODE_SPACE(address))    // 如果地址位于代码空间 则终止执行程序
		do_exit(SIGSEGV);
#endif  // 处理取消页面保护 参数指定页面在页表中的页表项指针 其计算方法是： （（address>>10）&0xffc） 计算指定地址的页面在页表中的偏移地址 (0xffffff000&*((address>>20)&0xffc) 取目录项中页表的地址值
	un_wp_page((unsigned long *)    // 其中（（address>>20）&oxffc）计算页面所在页表的目录项指针
		(((address>>10) & 0xffc) + (0xfffff000 &    // 两者相加即得指定地址对应页面得页表项指针 这里对共享的页面进行复制
		*((unsigned long *) ((address>>20) &0xffc)))));

}
// 写页面验证 若页面不可写 则复制页面 在 fork.c 第34行被调用
void write_verify(unsigned long address)
{
	unsigned long page;
    // 判断指定地址所对应页目录项的页表是否存在（P） 若不存在（P=0）则返回
	if (!( (page = *((unsigned long *) ((address>>20) & 0xffc)) )&1))
		return;
	page &= 0xfffff000; // 取页表的地址 加上指定地址的页面在页表中的页表项偏移值 得对应物理页面得页表项指针
	page += ((address>>10) & 0xffc);
	if ((3 & *(unsigned long *) page) == 1)  /* non-writeable, present */   // 如果该页面不可写（标志R/W没有置位） 则执行共享检验和复制页面操作（写时复制）
		un_wp_page((unsigned long *) page); // 不可写得 在内存中
	return;
}
// 取得一页空闲内存并映射到指定线性地址处 与get_free_page（）不同 get_free_page（）仅是申请取得了主内存区得一页物理内存 而该函数不仅是获取一页物理内存页面 还进一步调用put_page() 将物理页面映射到指定得线性地址处
void get_empty_page(unsigned long address)
{
	unsigned long tmp;
    // 若不能取得一空闲页面 或者不能将页面放置到指定地址处，则显示内存不够得信息 279行上 英文注释的含义是：即使执行get_free_page（）返回0 也无所谓 因为put_page（）中还会对此情况再次申请空闲物理页面得 见210行
	if (!(tmp=get_free_page()) || !put_page(tmp,address)) {
		free_page(tmp);		/* 0 is ok - ignored */
		oom();
	}
}

/*  // try_to_share（）在任务“p”中检查位于地址“address”处得页面 看页面是否存在 是否干净 如果是干净得话 就与当前任务共享
 * try_to_share() checks the page at address "address" in the task "p",
 * to see if it exists, and if it is clean. If so, share it with the current
 * task.
 *  // 注意！！这里我们已假定p不等于当前任务 并且他们共享同一个执行程序
 * NOTE! This assumes we have checked that p != current, and that they
 * share the same executable.
 */ // 尝试对进程指定地址处得页面进行共享操作 同时还验证指定得地址处是否已经申请了页面 若是则出错 死机 返回1-成功 0-失败
static int try_to_share(unsigned long address, struct task_struct * p)
{
	unsigned long from;
	unsigned long to;
	unsigned long from_page;
	unsigned long to_page;
	unsigned long phys_addr;

	from_page = to_page = ((address>>20) & 0xffc);  // 求指定内存地址得页目录项
	from_page += ((p->start_code>>20) & 0xffc);     // 计算进程p得代码起始地址所对应得页目录项
	to_page += ((current->start_code>>20) & 0xffc); // 计算当前进程中代码起始地址所对应得页目录项
/* is there a page-directory at from? */    // 在 from 处是否存在页目录
	from = *(unsigned long *) from_page;    // 取页目录项内容 如果该目录项无效（p=0）则返回 否则取该目录项对应页表地址保存在from中
	if (!(from & 1))
		return 0;
	from &= 0xfffff000;
	from_page = from + ((address>>10) & 0xffc); // 计算地址对应得页表指针值 并取出该页表项内容保存在 phys_addr 中
	phys_addr = *(unsigned long *) from_page;
/* is the page clean and present? */    // 页面干净并且存在码
	if ((phys_addr & 0x41) != 0x01)     // 0x41对应页表项中得Dirty 和 Present标志 如果页面不干净或无效则返回
		return 0;
	phys_addr &= 0xfffff000;    // 取页面得地址保存在 phys_addr 中 如果该页面地址不存在或小于内存低端（1MB）也返回退出
	if (phys_addr >= HIGH_MEMORY || phys_addr < LOW_MEM)
		return 0;
	to = *(unsigned long *) to_page;    // 取也买你目录项内容保存在to中 如果该目录项无效（p=0）则取空闲页面 并更新to_page 所指得目录项
	if (!(to & 1))
		if (to = get_free_page())
			*(unsigned long *) to_page = to | 7;
		else
			oom();
	to &= 0xfffff000;   // 取对应页表地址保存在 to 中 页表项地址 -> to_page 如果对应得页面已经存在 则出错 死机
	to_page = to + ((address>>10) & 0xffc);
	if (1 & *(unsigned long *) to_page)
		panic("try_to_share: to_page already exists");
/* share them: write-protect */ // 对他们进行共享处理 写保护
	*(unsigned long *) from_page &= ~2; // 对 p 进程中页面置写保护标志（置R/W=0 只读） 并且当前进程中得对应页表项指向它
	*(unsigned long *) to_page = *(unsigned long *) from_page;
	invalidate();   // 刷新页变换高速缓冲
	phys_addr -= LOW_MEM;   // 计算所操作页面得页面号 并将对应页面映射数组项中得引用递增1
	phys_addr >>= 12;
	mem_map[phys_addr]++;
	return 1;
}

/*  // share_page（）试图找到一个进程 它可以与当前进程共享页面 参数 address 是当前数据空间中期望共享得某页面地址
 * share_page() tries to find a process that could share a page with
 * the current one. Address is the address of the wanted page relative
 * to the current data space.
 *  // 首先我们通过检测 executable->i_count 来查证是否可行 如果有其他任务已共享该inode 则它应该大于1
 * We first check if it is at all feasible by checking executable->i_count.
 * It should be >1 if there are other tasks sharing this inode.
 */ // 共享页面 在缺页处理时看看能否共享页面 返回1-成功 0-失败
static int share_page(unsigned long address)
{
	struct task_struct ** p;
    // 如果是不可执行得 则返回 executable是执行进程得内存 i 节点结构
	if (!current->executable)
		return 0;
	if (current->executable->i_count < 2)   // 如果只能单独执行（executable->i_count =1） 也退出
		return 0;
	for (p = &LAST_TASK ; p > &FIRST_TASK ; --p) {  // 搜索任务数组中所有任务 寻找与当前进程可共享页面得进程 并尝试对指定地址得页面进行共享
		if (!*p)    // 如果该任务项空闲 则继续寻找
			continue;
		if (current == *p)  // 如果就是当前任务 也继续寻找
			continue;
		if ((*p)->executable != current->executable)    // 如果 executable 不等 也继续
			continue;
		if (try_to_share(address,*p))   // 尝试共享页面
			return 1;
	}
	return 0;
}
// 页异常终端处理调用得函数 处理缺页异常情况 在 page.s程序中被调用 参数 error_code是由CPU自动产生 address是页面线性地址
void do_no_page(unsigned long error_code,unsigned long address)
{
	int nr[4];
	unsigned long tmp;
	unsigned long page;
	int block,i;

	address &= 0xfffff000;  // 页面地址
	tmp = address - current->start_code;    // 首先算出执行线性地址在进程空间中相对于进程基址得偏移长度值
	if (!current->executable || tmp >= current->end_data) { // 若当前进程的 executable 空，或指定地址超出代码+数据长度 则申请一页物理内存 并映射到指定的线性地址处 executable是进程i节点结构 该值为0 表明进程刚开始设置 需要内存 而指定的地址超过代码加数据长度 表明进程在申请新的内存空间 也需要给予 因此就直接调用get_empty_page（）函数 申请一页物理内存并映射到指定线性地址处即可 start_code是进程代码地址 end_data 是代码加数据长度 对于Linux内核 它的代码段和数据段似乎起始基址是相同的
		get_empty_page(address);
		return;
	}
	if (share_page(tmp))    // 如果尝试共享页面成功 则退出
		return;
	if (!(page = get_free_page()))  // 取空闲页面 如果内存不够了 则显示内存不够 终止进程
		oom();
/* remember that 1 block is used for header */  // 记住 （程序头）要使用1个数据块
	block = 1 + tmp/BLOCK_SIZE;     // 首先计算缺页所在的数据块项 BLOCK_SIZE = 1024B 因此一页内存需要4个数据块
	for (i=0 ; i<4 ; block++,i++)   // 根据 i 节点信息，取数据块在设备上的对应的逻辑块号
		nr[i] = bmap(current->executable,block);
	bread_page(page,current->executable->i_dev,nr); // 读设备上一个页面的数据（4个逻辑块）到指定物理地址 page 处
	i = tmp + 4096 - current->end_data; // 在增加了一页内存后 该页内存的部分可能会超过进程的end_data位置 下面的循环即是对物理页面超出的部分进行清零处理
	tmp = page + 4096;
	while (i-- > 0) {
		tmp--;
		*(char *)tmp = 0;
	}   // 如果把物理页面映射到指定线性地址的操作成功 就返回 否则就释放内存页 显示内存不够
	if (put_page(page,address))
		return;
	free_page(page);
	oom();
}
// 物理内存初始化 参数：start_mem-可用作分页处理的物理内存起始位置（已去除RAMDISK所占内存空间等） end_mem：实际物理内存最大地址 在该版Linux内核中 最多能使用16MB内存 大于16MB的内存将不予考虑 弃置不用 另外 0-1MB内存空间用于内核系统（其实是0-640KB）
void mem_init(long start_mem, long end_mem)
{
	int i;

	HIGH_MEMORY = end_mem;  // 设置内存最高端
	for (i=0 ; i<PAGING_PAGES ; i++)    // 首先置所有页面为已占用（USED=100）状态
		mem_map[i] = USED;  // 即将页面映射数组全置成USED
	i = MAP_NR(start_mem);  // 然后计算可使用起始内存的页面号
	end_mem -= start_mem;   // 再计算可分页处理的内存块大小
	end_mem >>= 12;         // 从而计算出可用于分页处理的页面数
	while (end_mem-->0)     // 最后将这些可用页面对应的页面映射数组清零
		mem_map[i++]=0;
}
// 计算内存空闲页面数并显示
void calc_mem(void)
{
	int i,j,k,free=0;
	long * pg_tbl;
    // 扫描内存页面映射数组mem_map[] 获取空闲页面数并显示
	for(i=0 ; i<PAGING_PAGES ; i++)
		if (!mem_map[i]) free++;
	printk("%d pages free (of %d)\n\r",free,PAGING_PAGES);
	for(i=2 ; i<1024 ; i++) {   // 扫描所有也目录项（除0，1项） 如果页目录项有效 则统计对应页表中有效页面数 并显示
		if (1&pg_dir[i]) {
			pg_tbl=(long *) (0xfffff000 & pg_dir[i]);
			for(j=k=0 ; j<1024 ; j++)
				if (pg_tbl[j]&1)
					k++;
			printk("Pg-dir[%d] uses %d pages\n",i,k);
		}
	}
}
